#ifndef MAT4_HPP
#define MAT4_HPP

#include "Vec4.h"

template <typename T>
class MAT4
{
public:
    // constructors
    MAT4 ();    // initializes as identity matrix
    MAT4 (const VEC4<T> v1, const VEC4<T> v2, const VEC4<T> v3, const VEC4<T> v4, bool column_by_column = false);
    MAT4 (const T *mat_data, bool column_by_column = false);

    ~MAT4 ();

    // operators
    MAT4  operator = (const MAT4 &m);
    VEC4& operator [] (const int i);

    // for opengl access
    const T* GLMatrix (bool column_by_column = false);
private:
    
    void initCol (const VEC4<T> col_1, const VEC4<T> col_2, const VEC4<T> col_3, const VEC4<T> col_4);
    void initRow (const VEC4<T> row_1, const VEC4<T> row_2, const VEC4<T> row_3, const VEC4<T> row_4);
    void initCol (const T *colbycol);
    void initRow (const T *rowbyrow);

    void CommonInit ();
    void UpdateGLMatrixByCol ();
    void UpdateGLMatrixByRow ();

    VEC4<T> col1, col2, col3, col4;
    T *gl_m;
};

// constructors
template <typename T>
MAT4<T>::MAT4 ()
{
    col1 = VEC4<T>(1, 0, 0, 0);
    col2 = VEC4<T>(0, 1, 0, 0);
    col3 = VEC4<T>(0, 0, 1, 0);
    col4 = VEC4<T>(0, 0, 0, 1);
}

template <typename T>
MAT4<T>::MAT4 (const VEC4<T> v1, const VEC4<T> v2, const VEC4<T> v3, const VEC4<T> v4, bool column_by_column)
{
    CommonInit ();
    if (column_by_column)
        initCol(v1, v2, v3, v4);
    else
        initRow(v1, v2, v3, v4);
}

template <typename T>
MAT4<T>::MAT4 (const T *mat_data, bool column_by_column)
{
    CommonInit ();
    if (column_by_column)
        initCol(mat_data);
    else
        initRow(mat_data);
}

template <typename T>
MAT4<T>::~MAT4 ()
{
    delete []gl_m;
}   

// operators
template <typename T>
MAT4 MAT4<T>::operator = (const MAT4<T> &m)
{
    col1 = m[0];
    col2 = m[1];
    col3 = m[2];
    col4 = m[3];
}

template <typename T>
VEC4& MAT4<T>::operator [] (const int i)
{
    if (i <= 0)
        return col1;
    if (i == 1)
        return col2;
    if (i == 2)
        return col3;
    if (i >= 3)
        return col4;
}

// for opengl access
template <typename T>
const T* MAT4<T>::GLMatrix (bool column_by_column)
{
    if (column_by_column)
        UpdateGLMatrixByCol();
    else
        UpdateGLMatrixByRow();

    return gl_m;
}

// private functions

template <typename T>
void MAT4<T>::initCol (const VEC4<T> col_1, const VEC4<T> col_2, const VEC4<T> col_3, const VEC4<T> col_4)
{
    col1 = col_1;
    col2 = col_2;
    col3 = col_3;
    col4 = col_4;
}

template <typename T>
void MAT4<T>::initRow (const VEC4<T> row_1, const VEC4<T> row_2, const VEC4<T> row_3, const VEC4<T> row_4)
{
    col1 = VEC4<T>(row_1.x, row_2.x, row_3.x, row_4.x);
    col2 = VEC4<T>(row_1.y, row_2.y, row_3.y, row_4.y);
    col3 = VEC4<T>(row_1.z, row_2.z, row_3.z, row_4.z);
    col4 = VEC4<T>(row_1.t, row_2.t, row_3.t, row_4.t);
}

template <typename T>
void MAT4<T>::initRow (const T *rowbyrow)
{
    const T *r = rowbyrow;
    col1 = VEC4<T>(r[ 0], r[ 4], r[ 8], r[12]);
    col2 = VEC4<T>(r[ 1], r[ 5], r[ 9], r[13]);
    col3 = VEC4<T>(r[ 2], r[ 6], r[10], r[14]);
    col4 = VEC4<T>(r[ 3], r[ 7], r[11], r[15]);
}

template <typename T>
void  MAT4<T>::initCol (const T *colbycol)
{
    const T *c = colbycol;
    col1 = VEC4<T>(c[ 0], c[ 1], c[ 2], c[ 3]);
    col2 = VEC4<T>(c[ 4], c[ 5], c[ 6], c[ 7]);
    col3 = VEC4<T>(c[ 8], c[ 9], c[10], c[11]);
    col4 = VEC4<T>(c[12], c[13], c[14], c[15]);
}

template <typename T>
void MAT4<T>::CommonInit ()
{
    gl_m = new T[16];
}

template <typename T>
void MAT4<T>::UpdateGLMatrixByCol ()
{
    gl_m[ 0] = col1.x;
    gl_m[ 1] = col1.y;
    gl_m[ 2] = col1.z;
    gl_m[ 3] = col1.t;

    gl_m[ 4] = col2.x;
    gl_m[ 5] = col2.y;
    gl_m[ 6] = col2.z;
    gl_m[ 7] = col2.t;

    gl_m[ 8] = col3.x;
    gl_m[ 9] = col3.y;
    gl_m[10] = col3.z;
    gl_m[11] = col3.t;

    gl_m[12] = col4.x;
    gl_m[13] = col4.y;
    gl_m[14] = col4.z;
    gl_m[15] = col4.t;
}

template <typename T>
void MAT4<T>::UpdateGLMatrixByRow ()
{
    gl_m[ 0] = col1.x;
    gl_m[ 1] = col2.x;
    gl_m[ 2] = col3.x;
    gl_m[ 3] = col4.x;
         
    gl_m[ 4] = col1.y;
    gl_m[ 5] = col2.y;
    gl_m[ 6] = col3.y;
    gl_m[ 7] = col4.y;
         
    gl_m[ 8] = col1.z;
    gl_m[ 9] = col2.z;
    gl_m[10] = col3.z;
    gl_m[11] = col4.z;
         
    gl_m[12] = col1.t;
    gl_m[13] = col2.t;
    gl_m[14] = col3.t;
    gl_m[15] = col4.t;
}

#endif